1   /*
2    * Copyright (C) 2008 The Android Open Source Project
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.android.launcher;
18  
19  import android.app.WallpaperManager;
20  import android.content.Context;
21  import android.content.Intent;
22  import android.content.ComponentName;
23  import android.content.res.TypedArray;
24  import android.graphics.Canvas;
25  import android.graphics.Rect;
26  import android.graphics.Region;
27  import android.graphics.drawable.Drawable;
28  import android.util.AttributeSet;
29  import android.view.MotionEvent;
30  import android.view.VelocityTracker;
31  import android.view.View;
32  import android.view.ViewConfiguration;
33  import android.view.ViewGroup;
34  import android.view.ViewParent;
35  import android.widget.Scroller;
36  import android.widget.TextView;
37  import android.os.Parcelable;
38  import android.os.Parcel;
39  
40  import java.util.ArrayList;
41  
42  /**
43   * The workspace is a wide area with a wallpaper and a finite number of screens. Each
44   * screen contains a number of icons, folders or widgets the user can interact with.
45   * A workspace is meant to be used with a fixed width only.
46   */
47  public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
48      private static final int INVALID_SCREEN = -1;
49      
50      /**
51       * The velocity at which a fling gesture will cause us to snap to the next screen
52       */
53      private static final int SNAP_VELOCITY = 1000;
54  
55      private int mDefaultScreen;
56  
57      private final WallpaperManager mWallpaperManager;
58  
59      private boolean mFirstLayout = true;
60  
61      private int mCurrentScreen;
62      private int mNextScreen = INVALID_SCREEN;
63      private Scroller mScroller;
64      private VelocityTracker mVelocityTracker;
65  
66      /**
67       * CellInfo for the cell that is currently being dragged
68       */
69      private CellLayout.CellInfo mDragInfo;
70      
71      /**
72       * Target drop area calculated during last acceptDrop call.
73       */
74      private int[] mTargetCell = null;
75  
76      private float mLastMotionX;
77      private float mLastMotionY;
78  
79      private final static int TOUCH_STATE_REST = 0;
80      private final static int TOUCH_STATE_SCROLLING = 1;
81  
82      private int mTouchState = TOUCH_STATE_REST;
83  
84      private OnLongClickListener mLongClickListener;
85  
86      private Launcher mLauncher;
87      private DragController mDragger;
88      
89      /**
90       * Cache of vacant cells, used during drag events and invalidated as needed.
91       */
92      private CellLayout.CellInfo mVacantCache = null;
93      
94      private int[] mTempCell = new int[2];
95      private int[] mTempEstimate = new int[2];
96  
97      private boolean mAllowLongPress;
98      private boolean mLocked;
99  
100     private int mTouchSlop;
101     private int mMaximumVelocity;
102 
103     final Rect mDrawerBounds = new Rect();
104     final Rect mClipBounds = new Rect();
105     int mDrawerContentHeight;
106     int mDrawerContentWidth;
107 
108     /**
109      * Used to inflate the Workspace from XML.
110      *
111      * @param context The application's context.
112      * @param attrs The attribtues set containing the Workspace's customization values.
113      */
114     public Workspace(Context context, AttributeSet attrs) {
115         this(context, attrs, 0);
116     }
117 
118     /**
119      * Used to inflate the Workspace from XML.
120      *
121      * @param context The application's context.
122      * @param attrs The attribtues set containing the Workspace's customization values.
123      * @param defStyle Unused.
124      */
125     public Workspace(Context context, AttributeSet attrs, int defStyle) {
126         super(context, attrs, defStyle);
127 
128         mWallpaperManager = WallpaperManager.getInstance(context);
129         
130         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
131         mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
132         a.recycle();
133 
134         initWorkspace();
135     }
136 
137     /**
138      * Initializes various states for this workspace.
139      */
140     private void initWorkspace() {
141         mScroller = new Scroller(getContext());
142         mCurrentScreen = mDefaultScreen;
143         Launcher.setScreen(mCurrentScreen);
144 
145         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
146         mTouchSlop = configuration.getScaledTouchSlop();
147         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
148     }
149 
150     @Override
151     public void addView(View child, int index, LayoutParams params) {
152         if (!(child instanceof CellLayout)) {
153             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
154         }
155         super.addView(child, index, params);
156     }
157 
158     @Override
159     public void addView(View child) {
160         if (!(child instanceof CellLayout)) {
161             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
162         }
163         super.addView(child);
164     }
165 
166     @Override
167     public void addView(View child, int index) {
168         if (!(child instanceof CellLayout)) {
169             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
170         }
171         super.addView(child, index);
172     }
173 
174     @Override
175     public void addView(View child, int width, int height) {
176         if (!(child instanceof CellLayout)) {
177             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
178         }
179         super.addView(child, width, height);
180     }
181 
182     @Override
183     public void addView(View child, LayoutParams params) {
184         if (!(child instanceof CellLayout)) {
185             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
186         }
187         super.addView(child, params);
188     }
189 
190     /**
191      * @return The open folder on the current screen, or null if there is none
192      */
193     Folder getOpenFolder() {
194         CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
195         int count = currentScreen.getChildCount();
196         for (int i = 0; i < count; i++) {
197             View child = currentScreen.getChildAt(i);
198             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
199             if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
200                 return (Folder) child;
201             }
202         }
203         return null;
204     }
205 
206     ArrayList<Folder> getOpenFolders() {
207         final int screens = getChildCount();
208         ArrayList<Folder> folders = new ArrayList<Folder>(screens);
209 
210         for (int screen = 0; screen < screens; screen++) {
211             CellLayout currentScreen = (CellLayout) getChildAt(screen);
212             int count = currentScreen.getChildCount();
213             for (int i = 0; i < count; i++) {
214                 View child = currentScreen.getChildAt(i);
215                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
216                 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
217                     folders.add((Folder) child);
218                     break;
219                 }
220             }
221         }
222 
223         return folders;
224     }
225 
226     boolean isDefaultScreenShowing() {
227         return mCurrentScreen == mDefaultScreen;
228     }
229 
230     /**
231      * Returns the index of the currently displayed screen.
232      *
233      * @return The index of the currently displayed screen.
234      */
235     int getCurrentScreen() {
236         return mCurrentScreen;
237     }
238 
239     /**
240      * Sets the current screen.
241      *
242      * @param currentScreen
243      */
244     void setCurrentScreen(int currentScreen) {
245         clearVacantCache();
246         mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
247         scrollTo(mCurrentScreen * getWidth(), 0);
248         invalidate();
249     }
250 
251     /**
252      * Adds the specified child in the current screen. The position and dimension of
253      * the child are defined by x, y, spanX and spanY.
254      *
255      * @param child The child to add in one of the workspace's screens.
256      * @param x The X position of the child in the screen's grid.
257      * @param y The Y position of the child in the screen's grid.
258      * @param spanX The number of cells spanned horizontally by the child.
259      * @param spanY The number of cells spanned vertically by the child.
260      */
261     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
262         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
263     }
264 
265     /**
266      * Adds the specified child in the current screen. The position and dimension of
267      * the child are defined by x, y, spanX and spanY.
268      *
269      * @param child The child to add in one of the workspace's screens.
270      * @param x The X position of the child in the screen's grid.
271      * @param y The Y position of the child in the screen's grid.
272      * @param spanX The number of cells spanned horizontally by the child.
273      * @param spanY The number of cells spanned vertically by the child.
274      * @param insert When true, the child is inserted at the beginning of the children list.
275      */
276     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
277         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
278     }
279 
280     /**
281      * Adds the specified child in the specified screen. The position and dimension of
282      * the child are defined by x, y, spanX and spanY.
283      *
284      * @param child The child to add in one of the workspace's screens.
285      * @param screen The screen in which to add the child.
286      * @param x The X position of the child in the screen's grid.
287      * @param y The Y position of the child in the screen's grid.
288      * @param spanX The number of cells spanned horizontally by the child.
289      * @param spanY The number of cells spanned vertically by the child.
290      */
291     void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
292         addInScreen(child, screen, x, y, spanX, spanY, false);
293     }
294 
295     /**
296      * Adds the specified child in the specified screen. The position and dimension of
297      * the child are defined by x, y, spanX and spanY.
298      *
299      * @param child The child to add in one of the workspace's screens.
300      * @param screen The screen in which to add the child.
301      * @param x The X position of the child in the screen's grid.
302      * @param y The Y position of the child in the screen's grid.
303      * @param spanX The number of cells spanned horizontally by the child.
304      * @param spanY The number of cells spanned vertically by the child.
305      * @param insert When true, the child is inserted at the beginning of the children list.
306      */
307     void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
308         if (screen < 0 || screen >= getChildCount()) {
309             throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
310         }
311 
312         clearVacantCache();
313 
314         final CellLayout group = (CellLayout) getChildAt(screen);
315         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
316         if (lp == null) {
317             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
318         } else {
319             lp.cellX = x;
320             lp.cellY = y;
321             lp.cellHSpan = spanX;
322             lp.cellVSpan = spanY;
323         }
324         group.addView(child, insert ? 0 : -1, lp);
325         if (!(child instanceof Folder)) {
326             child.setOnLongClickListener(mLongClickListener);
327         }
328     }
329 
330     void addWidget(View view, Widget widget, boolean insert) {
331         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
332                 widget.spanY, insert);
333     }
334 
335     CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
336         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
337         if (group != null) {
338             return group.findAllVacantCells(occupied, null);
339         }
340         return null;
341     }
342     
343     CellLayout.CellInfo findAllVacantCellsFromModel() {
344         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
345         if (group != null) {
346             int countX = group.getCountX();
347             int countY = group.getCountY();
348             boolean occupied[][] = new boolean[countX][countY];
349             Launcher.getModel().findAllOccupiedCells(occupied, countX, countY, mCurrentScreen);
350             return group.findAllVacantCellsFromOccupied(occupied, countX, countY);
351         }
352         return null;
353     }
354 
355     private void clearVacantCache() {
356         if (mVacantCache != null) {
357             mVacantCache.clearVacantCells();
358             mVacantCache = null;
359         }
360     }
361 
362     /**
363      * Registers the specified listener on each screen contained in this workspace.
364      *
365      * @param l The listener used to respond to long clicks.
366      */
367     @Override
368     public void setOnLongClickListener(OnLongClickListener l) {
369         mLongClickListener = l;
370         final int count = getChildCount();
371         for (int i = 0; i < count; i++) {
372             getChildAt(i).setOnLongClickListener(l);
373         }
374     }
375 
376     private void updateWallpaperOffset() {
377         updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
378     }
379 
380     private void updateWallpaperOffset(int scrollRange) {
381         mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
382         mWallpaperManager.setWallpaperOffsets(getWindowToken(), mScrollX / (float) scrollRange, 0);
383     }
384     
385     @Override
386     public void computeScroll() {
387         if (mScroller.computeScrollOffset()) {
388             mScrollX = mScroller.getCurrX();
389             mScrollY = mScroller.getCurrY();
390             updateWallpaperOffset();
391             postInvalidate();
392         } else if (mNextScreen != INVALID_SCREEN) {
393             mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
394             Launcher.setScreen(mCurrentScreen);
395             mNextScreen = INVALID_SCREEN;
396             clearChildrenCache();
397         }
398     }
399 
400     @Override
401     public boolean isOpaque() {
402         return false;
403     }
404 
405     @Override
406     protected void dispatchDraw(Canvas canvas) {
407         boolean restore = false;
408 
409         // If the all apps drawer is open and the drawing region for the workspace
410         // is contained within the drawer's bounds, we skip the drawing. This requires
411         // the drawer to be fully opaque.
412         if (mLauncher.isDrawerUp()) {
413             final Rect clipBounds = mClipBounds;
414             canvas.getClipBounds(clipBounds);
415             clipBounds.offset(-mScrollX, -mScrollY);
416             if (mDrawerBounds.contains(clipBounds)) {
417                 return;
418             }
419         } else if (mLauncher.isDrawerMoving()) {
420             restore = true;
421             canvas.save(Canvas.CLIP_SAVE_FLAG);
422 
423             final View view = mLauncher.getDrawerHandle();
424             final int top = view.getTop() + view.getHeight();
425 
426             canvas.clipRect(mScrollX, top, mScrollX + mDrawerContentWidth,
427                     top + mDrawerContentHeight, Region.Op.DIFFERENCE);
428         }
429 
430         // ViewGroup.dispatchDraw() supports many features we don't need:
431         // clip to padding, layout animation, animation listener, disappearing
432         // children, etc. The following implementation attempts to fast-track
433         // the drawing dispatch by drawing only what we know needs to be drawn.
434 
435         boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
436         // If we are not scrolling or flinging, draw only the current screen
437         if (fastDraw) {
438             drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
439         } else {
440             final long drawingTime = getDrawingTime();
441             // If we are flinging, draw only the current screen and the target screen
442             if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
443                     Math.abs(mCurrentScreen - mNextScreen) == 1) {
444                 drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
445                 drawChild(canvas, getChildAt(mNextScreen), drawingTime);
446             } else {
447                 // If we are scrolling, draw all of our children
448                 final int count = getChildCount();
449                 for (int i = 0; i < count; i++) {
450                     drawChild(canvas, getChildAt(i), drawingTime);
451                 }
452             }
453         }
454 
455         if (restore) {
456             canvas.restore();
457         }
458     }
459 
460     @Override
461     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
462         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
463 
464         final int width = MeasureSpec.getSize(widthMeasureSpec);
465         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
466         if (widthMode != MeasureSpec.EXACTLY) {
467             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
468         }
469 
470         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
471         if (heightMode != MeasureSpec.EXACTLY) {
472             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
473         }
474 
475         // The children are given the same width and height as the workspace
476         final int count = getChildCount();
477         for (int i = 0; i < count; i++) {
478             getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
479         }
480 
481         if (mFirstLayout) {
482             scrollTo(mCurrentScreen * width, 0);
483             updateWallpaperOffset(width * (getChildCount() - 1));
484             mFirstLayout = false;
485         }
486     }
487 
488     @Override
489     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
490         int childLeft = 0;
491 
492         final int count = getChildCount();
493         for (int i = 0; i < count; i++) {
494             final View child = getChildAt(i);
495             if (child.getVisibility() != View.GONE) {
496                 final int childWidth = child.getMeasuredWidth();
497                 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
498                 childLeft += childWidth;
499             }
500         }
501     }
502 
503     @Override
504     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
505         int screen = indexOfChild(child);
506         if (screen != mCurrentScreen || !mScroller.isFinished()) {
507             if (!mLauncher.isWorkspaceLocked()) {
508                 snapToScreen(screen);
509             }
510             return true;
511         }
512         return false;
513     }
514 
515     @Override
516     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
517         if (mLauncher.isDrawerDown()) {
518             final Folder openFolder = getOpenFolder();
519             if (openFolder != null) {
520                 return openFolder.requestFocus(direction, previouslyFocusedRect);
521             } else {
522                 int focusableScreen;
523                 if (mNextScreen != INVALID_SCREEN) {
524                     focusableScreen = mNextScreen;
525                 } else {
526                     focusableScreen = mCurrentScreen;
527                 }
528                 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
529             }
530         }
531         return false;
532     }
533 
534     @Override
535     public boolean dispatchUnhandledMove(View focused, int direction) {
536         if (direction == View.FOCUS_LEFT) {
537             if (getCurrentScreen() > 0) {
538                 snapToScreen(getCurrentScreen() - 1);
539                 return true;
540             }
541         } else if (direction == View.FOCUS_RIGHT) {
542             if (getCurrentScreen() < getChildCount() - 1) {
543                 snapToScreen(getCurrentScreen() + 1);
544                 return true;
545             }
546         }
547         return super.dispatchUnhandledMove(focused, direction);
548     }
549 
550     @Override
551     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
552         if (mLauncher.isDrawerDown()) {
553             final Folder openFolder = getOpenFolder();
554             if (openFolder == null) {
555                 getChildAt(mCurrentScreen).addFocusables(views, direction);
556                 if (direction == View.FOCUS_LEFT) {
557                     if (mCurrentScreen > 0) {
558                         getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
559                     }
560                 } else if (direction == View.FOCUS_RIGHT){
561                     if (mCurrentScreen < getChildCount() - 1) {
562                         getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
563                     }
564                 }
565             } else {
566                 openFolder.addFocusables(views, direction);
567             }
568         }
569     }
570 
571     @Override
572     public boolean onInterceptTouchEvent(MotionEvent ev) {
573         if (mLocked || !mLauncher.isDrawerDown()) {
574             return true;
575         }
576 
577         /*
578          * This method JUST determines whether we want to intercept the motion.
579          * If we return true, onTouchEvent will be called and we do the actual
580          * scrolling there.
581          */
582 
583         /*
584          * Shortcut the most recurring case: the user is in the dragging
585          * state and he is moving his finger.  We want to intercept this
586          * motion.
587          */
588         final int action = ev.getAction();
589         if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
590             return true;
591         }
592 
593         final float x = ev.getX();
594         final float y = ev.getY();
595 
596         switch (action) {
597             case MotionEvent.ACTION_MOVE:
598                 /*
599                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
600                  * whether the user has moved far enough from his original down touch.
601                  */
602 
603                 /*
604                  * Locally do absolute value. mLastMotionX is set to the y value
605                  * of the down event.
606                  */
607                 final int xDiff = (int) Math.abs(x - mLastMotionX);
608                 final int yDiff = (int) Math.abs(y - mLastMotionY);
609 
610                 final int touchSlop = mTouchSlop;
611                 boolean xMoved = xDiff > touchSlop;
612                 boolean yMoved = yDiff > touchSlop;
613                 
614                 if (xMoved || yMoved) {
615                     
616                     if (xMoved) {
617                         // Scroll if the user moved far enough along the X axis
618                         mTouchState = TOUCH_STATE_SCROLLING;
619                         enableChildrenCache();
620                     }
621                     // Either way, cancel any pending longpress
622                     if (mAllowLongPress) {
623                         mAllowLongPress = false;
624                         // Try canceling the long press. It could also have been scheduled
625                         // by a distant descendant, so use the mAllowLongPress flag to block
626                         // everything
627                         final View currentScreen = getChildAt(mCurrentScreen);
628                         currentScreen.cancelLongPress();
629                     }
630                 }
631                 break;
632 
633             case MotionEvent.ACTION_DOWN:
634                 // Remember location of down touch
635                 mLastMotionX = x;
636                 mLastMotionY = y;
637                 mAllowLongPress = true;
638 
639                 /*
640                  * If being flinged and user touches the screen, initiate drag;
641                  * otherwise don't.  mScroller.isFinished should be false when
642                  * being flinged.
643                  */
644                 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
645                 break;
646 
647             case MotionEvent.ACTION_CANCEL:
648             case MotionEvent.ACTION_UP:
649                 // Release the drag
650                 clearChildrenCache();
651                 mTouchState = TOUCH_STATE_REST;
652                 mAllowLongPress = false;
653                 break;
654         }
655 
656         /*
657          * The only time we want to intercept motion events is if we are in the
658          * drag mode.
659          */
660         return mTouchState != TOUCH_STATE_REST;
661     }
662 
663     void enableChildrenCache() {
664         final int count = getChildCount();
665         for (int i = 0; i < count; i++) {
666             final CellLayout layout = (CellLayout) getChildAt(i);
667             layout.setChildrenDrawnWithCacheEnabled(true);
668             layout.setChildrenDrawingCacheEnabled(true);
669         }
670     }
671 
672     void clearChildrenCache() {
673         final int count = getChildCount();
674         for (int i = 0; i < count; i++) {
675             final CellLayout layout = (CellLayout) getChildAt(i);
676             layout.setChildrenDrawnWithCacheEnabled(false);
677         }
678     }
679 
680     @Override
681     public boolean onTouchEvent(MotionEvent ev) {
682         if (mLocked || !mLauncher.isDrawerDown()) {
683             return true;
684         }
685 
686         if (mVelocityTracker == null) {
687             mVelocityTracker = VelocityTracker.obtain();
688         }
689         mVelocityTracker.addMovement(ev);
690 
691         final int action = ev.getAction();
692         final float x = ev.getX();
693 
694         switch (action) {
695         case MotionEvent.ACTION_DOWN:
696             /*
697              * If being flinged and user touches, stop the fling. isFinished
698              * will be false if being flinged.
699              */
700             if (!mScroller.isFinished()) {
701                 mScroller.abortAnimation();
702             }
703 
704             // Remember where the motion event started
705             mLastMotionX = x;
706             break;
707         case MotionEvent.ACTION_MOVE:
708             if (mTouchState == TOUCH_STATE_SCROLLING) {
709                 // Scroll to follow the motion event
710                 final int deltaX = (int) (mLastMotionX - x);
711                 mLastMotionX = x;
712 
713                 if (deltaX < 0) {
714                     if (mScrollX > 0) {
715                         scrollBy(Math.max(-mScrollX, deltaX), 0);
716                         updateWallpaperOffset();
717                     }
718                 } else if (deltaX > 0) {
719                     final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
720                             mScrollX - getWidth();
721                     if (availableToScroll > 0) {
722                         scrollBy(Math.min(availableToScroll, deltaX), 0);
723                         updateWallpaperOffset();
724                     }
725                 }
726             }
727             break;
728         case MotionEvent.ACTION_UP:
729             if (mTouchState == TOUCH_STATE_SCROLLING) {
730                 final VelocityTracker velocityTracker = mVelocityTracker;
731                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
732                 int velocityX = (int) velocityTracker.getXVelocity();
733 
734                 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
735                     // Fling hard enough to move left
736                     snapToScreen(mCurrentScreen - 1);
737                 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
738                     // Fling hard enough to move right
739                     snapToScreen(mCurrentScreen + 1);
740                 } else {
741                     snapToDestination();
742                 }
743 
744                 if (mVelocityTracker != null) {
745                     mVelocityTracker.recycle();
746                     mVelocityTracker = null;
747                 }
748             }
749             mTouchState = TOUCH_STATE_REST;
750             break;
751         case MotionEvent.ACTION_CANCEL:
752             mTouchState = TOUCH_STATE_REST;
753         }
754 
755         return true;
756     }
757 
758     private void snapToDestination() {
759         final int screenWidth = getWidth();
760         final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
761 
762         snapToScreen(whichScreen);
763     }
764 
765     void snapToScreen(int whichScreen) {
766         if (!mScroller.isFinished()) return;
767 
768         clearVacantCache();
769         enableChildrenCache();
770 
771         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
772         boolean changingScreens = whichScreen != mCurrentScreen;
773         
774         mNextScreen = whichScreen;
775         
776         View focusedChild = getFocusedChild();
777         if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
778             focusedChild.clearFocus();
779         }
780         
781         final int newX = whichScreen * getWidth();
782         final int delta = newX - mScrollX;
783         mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
784         invalidate();
785     }
786 
787     void startDrag(CellLayout.CellInfo cellInfo) {
788         View child = cellInfo.cell;
789         
790         // Make sure the drag was started by a long press as opposed to a long click.
791         // Note that Search takes focus when clicked rather than entering touch mode
792         if (!child.isInTouchMode() && !(child instanceof Search)) {
793             return;
794         }
795         
796         mDragInfo = cellInfo;
797         mDragInfo.screen = mCurrentScreen;
798         
799         CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
800 
801         current.onDragChild(child);
802         mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
803         invalidate();
804     }
805 
806     @Override
807     protected Parcelable onSaveInstanceState() {
808         final SavedState state = new SavedState(super.onSaveInstanceState());
809         state.currentScreen = mCurrentScreen;
810         return state;
811     }
812 
813     @Override
814     protected void onRestoreInstanceState(Parcelable state) {
815         SavedState savedState = (SavedState) state;
816         super.onRestoreInstanceState(savedState.getSuperState());
817         if (savedState.currentScreen != -1) {
818             mCurrentScreen = savedState.currentScreen;
819             Launcher.setScreen(mCurrentScreen);
820         }
821     }
822 
823     void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
824             boolean insertAtFirst) {
825         final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
826         final int[] result = new int[2];
827 
828         layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
829         onDropExternal(result[0], result[1], info, layout, insertAtFirst);
830     }
831 
832     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
833         final CellLayout cellLayout = getCurrentDropLayout();
834         if (source != this) {
835             onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
836         } else {
837             // Move internally
838             if (mDragInfo != null) {
839                 final View cell = mDragInfo.cell;
840                 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;                
841                 if (index != mDragInfo.screen) {
842                     final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
843                     originalCellLayout.removeView(cell);
844                     cellLayout.addView(cell);
845                 }
846                 mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
847                         mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
848                 cellLayout.onDropChild(cell, mTargetCell);
849 
850                 final ItemInfo info = (ItemInfo)cell.getTag();
851                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
852                 LauncherModel.moveItemInDatabase(mLauncher, info,
853                         LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY);
854             }
855         }
856     }
857 
858     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
859             Object dragInfo) {
860         clearVacantCache();
861     }
862 
863     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
864             Object dragInfo) {
865     }
866 
867     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
868             Object dragInfo) {
869         clearVacantCache();
870     }
871 
872     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
873         onDropExternal(x, y, dragInfo, cellLayout, false);
874     }
875     
876     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
877             boolean insertAtFirst) {
878         // Drag from somewhere else
879         ItemInfo info = (ItemInfo) dragInfo;
880 
881         View view;
882 
883         switch (info.itemType) {
884         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
885         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
886             if (info.container == NO_ID) {
887                 // Came from all apps -- make a copy
888                 info = new ApplicationInfo((ApplicationInfo) info);
889             }
890             view = mLauncher.createShortcut(R.layout.application, cellLayout,
891                     (ApplicationInfo) info);
892             break;
893         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
894             view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
895                     (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
896             break;
897         default:
898             throw new IllegalStateException("Unknown item type: " + info.itemType);
899         }
900 
901         cellLayout.addView(view, insertAtFirst ? 0 : -1);
902         view.setOnLongClickListener(mLongClickListener);
903         mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
904         cellLayout.onDropChild(view, mTargetCell);
905         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
906 
907         final LauncherModel model = Launcher.getModel();
908         model.addDesktopItem(info);
909         LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
910                 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
911     }
912     
913     /**
914      * Return the current {@link CellLayout}, correctly picking the destination
915      * screen while a scroll is in progress.
916      */
917     private CellLayout getCurrentDropLayout() {
918         int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
919         return (CellLayout) getChildAt(index);
920     }
921 
922     /**
923      * {@inheritDoc}
924      */
925     public boolean acceptDrop(DragSource source, int x, int y,
926             int xOffset, int yOffset, Object dragInfo) {
927         final CellLayout layout = getCurrentDropLayout();
928         final CellLayout.CellInfo cellInfo = mDragInfo;
929         final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
930         final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
931 
932         if (mVacantCache == null) {
933             final View ignoreView = cellInfo == null ? null : cellInfo.cell;
934             mVacantCache = layout.findAllVacantCells(null, ignoreView);
935         }
936 
937         return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
938     }
939     
940     /**
941      * {@inheritDoc}
942      */
943     public Rect estimateDropLocation(int x, int y, int xOffset, int yOffset, Rect recycle) {
944         final CellLayout layout = getCurrentDropLayout();
945         
946         final CellLayout.CellInfo cellInfo = mDragInfo;
947         final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
948         final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
949         final View ignoreView = cellInfo == null ? null : cellInfo.cell;
950         
951         final Rect location = recycle != null ? recycle : new Rect();
952         
953         // Find drop cell and convert into rectangle
954         int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
955                 spanX, spanY, ignoreView, layout, mTempCell);
956         
957         if (dropCell == null) {
958             return null;
959         }
960         
961         layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
962         location.left = mTempEstimate[0];
963         location.top = mTempEstimate[1];
964         
965         layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
966         location.right = mTempEstimate[0];
967         location.bottom = mTempEstimate[1];
968         
969         return location;
970     }
971 
972     /**
973      * Calculate the nearest cell where the given object would be dropped.
974      */
975     private int[] estimateDropCell(int pixelX, int pixelY,
976             int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
977         // Create vacant cell cache if none exists
978         if (mVacantCache == null) {
979             mVacantCache = layout.findAllVacantCells(null, ignoreView);
980         }
981 
982         // Find the best target drop location
983         return layout.findNearestVacantArea(pixelX, pixelY, spanX, spanY, mVacantCache, recycle);
984     }
985     
986     void setLauncher(Launcher launcher) {
987         mLauncher = launcher;
988     }
989 
990     public void setDragger(DragController dragger) {
991         mDragger = dragger;
992     }
993 
994     public void onDropCompleted(View target, boolean success) {
995         // This is a bit expensive but safe
996         clearVacantCache();
997 
998         if (success){
999             if (target != this && mDragInfo != null) {
1000                 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1001                 cellLayout.removeView(mDragInfo.cell);
1002                 final Object tag = mDragInfo.cell.getTag();
1003                 Launcher.getModel().removeDesktopItem((ItemInfo) tag);
1004             }
1005         } else {
1006             if (mDragInfo != null) {
1007                 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1008                 cellLayout.onDropAborted(mDragInfo.cell);
1009             }
1010         }
1011 
1012         mDragInfo = null;
1013     }
1014 
1015     public void scrollLeft() {
1016         clearVacantCache();
1017         if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
1018             snapToScreen(mCurrentScreen - 1);
1019         }
1020     }
1021 
1022     public void scrollRight() {
1023         clearVacantCache();
1024         if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
1025                 mScroller.isFinished()) {
1026             snapToScreen(mCurrentScreen + 1);
1027         }
1028     }
1029 
1030     public int getScreenForView(View v) {
1031         int result = -1;
1032         if (v != null) {
1033             ViewParent vp = v.getParent();
1034             int count = getChildCount();
1035             for (int i = 0; i < count; i++) {
1036                 if (vp == getChildAt(i)) {
1037                     return i;
1038                 }
1039             }
1040         }
1041         return result;
1042     }
1043 
1044     /**
1045      * Find a search widget on the given screen
1046      */
1047     private Search findSearchWidget(CellLayout screen) {
1048         final int count = screen.getChildCount();
1049         for (int i = 0; i < count; i++) {
1050             View v = screen.getChildAt(i);
1051             if (v instanceof Search) {
1052                 return (Search) v;
1053             }
1054         }
1055         return null;
1056     }
1057 
1058     /**
1059      * Gets the first search widget on the current screen, if there is one.
1060      * Returns <code>null</code> otherwise.
1061      */
1062     public Search findSearchWidgetOnCurrentScreen() {
1063         CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
1064         return findSearchWidget(currentScreen);
1065     }
1066 
1067     public Folder getFolderForTag(Object tag) {
1068         int screenCount = getChildCount();
1069         for (int screen = 0; screen < screenCount; screen++) {
1070             CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1071             int count = currentScreen.getChildCount();
1072             for (int i = 0; i < count; i++) {
1073                 View child = currentScreen.getChildAt(i);
1074                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1075                 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1076                     Folder f = (Folder) child;
1077                     if (f.getInfo() == tag) {
1078                         return f;
1079                     }
1080                 }
1081             }
1082         }
1083         return null;
1084     }
1085 
1086     public View getViewForTag(Object tag) {
1087         int screenCount = getChildCount();
1088         for (int screen = 0; screen < screenCount; screen++) {
1089             CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1090             int count = currentScreen.getChildCount();
1091             for (int i = 0; i < count; i++) {
1092                 View child = currentScreen.getChildAt(i);
1093                 if (child.getTag() == tag) {
1094                     return child;
1095                 }
1096             }
1097         }
1098         return null;
1099     }
1100 
1101     /**
1102      * Unlocks the SlidingDrawer so that touch events are processed.
1103      *
1104      * @see #lock()
1105      */
1106     public void unlock() {
1107         mLocked = false;
1108     }
1109 
1110     /**
1111      * Locks the SlidingDrawer so that touch events are ignores.
1112      *
1113      * @see #unlock()
1114      */
1115     public void lock() {
1116         mLocked = true;
1117     }
1118     
1119     /**
1120      * @return True is long presses are still allowed for the current touch
1121      */
1122     public boolean allowLongPress() {
1123         return mAllowLongPress;
1124     }
1125     
1126     /**
1127      * Set true to allow long-press events to be triggered, usually checked by
1128      * {@link Launcher} to accept or block dpad-initiated long-presses.
1129      */
1130     public void setAllowLongPress(boolean allowLongPress) {
1131         mAllowLongPress = allowLongPress;
1132     }
1133 
1134     void removeShortcutsForPackage(String packageName) {
1135         final ArrayList<View> childrenToRemove = new ArrayList<View>();
1136         final LauncherModel model = Launcher.getModel();
1137         final int count = getChildCount();
1138 
1139         for (int i = 0; i < count; i++) {
1140             final CellLayout layout = (CellLayout) getChildAt(i);
1141             int childCount = layout.getChildCount();
1142 
1143             childrenToRemove.clear();
1144 
1145             for (int j = 0; j < childCount; j++) {
1146                 final View view = layout.getChildAt(j);
1147                 Object tag = view.getTag();
1148 
1149                 if (tag instanceof ApplicationInfo) {
1150                     final ApplicationInfo info = (ApplicationInfo) tag;
1151                     // We need to check for ACTION_MAIN otherwise getComponent() might
1152                     // return null for some shortcuts (for instance, for shortcuts to
1153                     // web pages.)
1154                     final Intent intent = info.intent;
1155                     final ComponentName name = intent.getComponent();
1156 
1157                     if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1158                             name != null && packageName.equals(name.getPackageName())) {
1159                         model.removeDesktopItem(info);
1160                         LauncherModel.deleteItemFromDatabase(mLauncher, info);
1161                         childrenToRemove.add(view);
1162                     }
1163                 } else if (tag instanceof UserFolderInfo) {
1164                     final UserFolderInfo info = (UserFolderInfo) tag;
1165                     final ArrayList<ApplicationInfo> contents = info.contents;
1166                     final ArrayList<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(1);
1167                     final int contentsCount = contents.size();
1168                     boolean removedFromFolder = false;
1169 
1170                     for (int k = 0; k < contentsCount; k++) {
1171                         final ApplicationInfo appInfo = contents.get(k);
1172                         final Intent intent = appInfo.intent;
1173                         final ComponentName name = intent.getComponent();
1174 
1175                         if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1176                                 name != null && packageName.equals(name.getPackageName())) {
1177                             toRemove.add(appInfo);
1178                             LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
1179                             removedFromFolder = true;
1180                         }
1181                     }
1182 
1183                     contents.removeAll(toRemove);
1184                     if (removedFromFolder) {
1185                         final Folder folder = getOpenFolder();
1186                         if (folder != null) folder.notifyDataSetChanged();
1187                     }
1188                 }
1189             }
1190 
1191             childCount = childrenToRemove.size();
1192             for (int j = 0; j < childCount; j++) {
1193                 layout.removeViewInLayout(childrenToRemove.get(j));
1194             }
1195 
1196             if (childCount > 0) {
1197                 layout.requestLayout();
1198                 layout.invalidate();
1199             }
1200         }
1201     }
1202 
1203     void updateShortcutsForPackage(String packageName) {
1204         final int count = getChildCount();
1205         for (int i = 0; i < count; i++) {
1206             final CellLayout layout = (CellLayout) getChildAt(i);
1207             int childCount = layout.getChildCount();
1208             for (int j = 0; j < childCount; j++) {
1209                 final View view = layout.getChildAt(j);
1210                 Object tag = view.getTag();
1211                 if (tag instanceof ApplicationInfo) {
1212                     ApplicationInfo info = (ApplicationInfo) tag;
1213                     // We need to check for ACTION_MAIN otherwise getComponent() might
1214                     // return null for some shortcuts (for instance, for shortcuts to
1215                     // web pages.)
1216                     final Intent intent = info.intent;
1217                     final ComponentName name = intent.getComponent();
1218                     if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1219                             Intent.ACTION_MAIN.equals(intent.getAction()) && name != null &&
1220                             packageName.equals(name.getPackageName())) {
1221 
1222                         final Drawable icon = Launcher.getModel().getApplicationInfoIcon(
1223                                 mLauncher.getPackageManager(), info);
1224                         if (icon != null && icon != info.icon) {
1225                             info.icon.setCallback(null);
1226                             info.icon = Utilities.createIconThumbnail(icon, mContext);
1227                             info.filtered = true;
1228                             ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null,
1229                                     info.icon, null, null);
1230                         }
1231                     }
1232                 }
1233             }
1234         }
1235     }
1236 
1237     void moveToDefaultScreen() {
1238         snapToScreen(mDefaultScreen);
1239         getChildAt(mDefaultScreen).requestFocus();
1240     }
1241 
1242     public static class SavedState extends BaseSavedState {
1243         int currentScreen = -1;
1244 
1245         SavedState(Parcelable superState) {
1246             super(superState);
1247         }
1248 
1249         private SavedState(Parcel in) {
1250             super(in);
1251             currentScreen = in.readInt();
1252         }
1253 
1254         @Override
1255         public void writeToParcel(Parcel out, int flags) {
1256             super.writeToParcel(out, flags);
1257             out.writeInt(currentScreen);
1258         }
1259 
1260         public static final Parcelable.Creator<SavedState> CREATOR =
1261                 new Parcelable.Creator<SavedState>() {
1262             public SavedState createFromParcel(Parcel in) {
1263                 return new SavedState(in);
1264             }
1265 
1266             public SavedState[] newArray(int size) {
1267                 return new SavedState[size];
1268             }
1269         };
1270     }
1271 }